package com.magenta.gis.lira.xml;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StreamQuery {
	
	private final static Logger LOG = LoggerFactory.getLogger(StreamQuery.class);
	
	private final XMLStreamReader streamReader;
	private List<String> xmlPath;
	private List<String> xmlTagsList;
	
	private Map<String, Object> tmpResult;

	public StreamQuery(InputStream in, List<String> xmlPath,
			List<String> xmlTagsList) throws XMLStreamException, FactoryConfigurationError {
		this.streamReader = XMLInputFactory.newInstance().createXMLStreamReader(in);
		this.xmlPath = new ArrayList<String>(xmlPath);
		this.xmlTagsList = new ArrayList<String>(xmlTagsList);
	}
	
	private Object readNextObject() throws XMLStreamException, FactoryConfigurationError {
		if ( streamReader.hasNext() ) {
			Stack<Object> stackOfContexts = new Stack<Object>();
			ThreadLocal<Object> localContext = new ThreadLocal<Object>();
			int event = streamReader.nextTag();
			mainCycle: while ( true ) {
				switch ( event ) {
				case XMLEvent.START_ELEMENT:
					//CALL getElementText
					parseNextObject(streamReader, streamReader.getLocalName(), localContext);
					if ( streamReader.isEndElement() ) {
						if ( !stackOfContexts.isEmpty() ) {
							localContext.set(stackOfContexts.peek());
						}
					} else {
						stackOfContexts.push(localContext.get());
					}
					break;
				case XMLEvent.END_ELEMENT:
					stackOfContexts.pop();
					if ( !stackOfContexts.isEmpty() ) {
						localContext.set(stackOfContexts.peek());
					}
					break;
				}

				//try {
					while ( streamReader.hasNext() ) {
						event = streamReader.next();
						if ( streamReader.hasName() ) {
							break;
						}
					}
					if ( !streamReader.hasNext() ) {
						break mainCycle;
					}
				//} catch (XMLStreamException e) {
				//	//CHECK END OF DOCUMENT: CHARS AFTER END OF ROOT ELEMENT ISSUE
				//	while ( event != XMLEvent.START_ELEMENT || event != XMLEvent.END_ELEMENT ) {
				//		if ( !streamReader.hasNext() ) {
				//			break mainCycle;
				//		}
				//		event = streamReader.next();
				//	}
				//	throw e;
				//}
			}
		}
		return null;
	}

	private int parseNextObject(XMLStreamReader streamReader, String localName, ThreadLocal<Object> context) throws XMLStreamException {
		if ( context.get() == null ) {//FIRST LEVEL
			if ( !xmlPath.isEmpty() ) {
				if ( xmlPath.get(0).equals(localName) ) {
					xmlPath.remove(0);
				}
			}
			context.set(localName);
		} else {
			//FILTERING XML PATH
			if ( !xmlPath.isEmpty() ) {
				if ( xmlPath.get(0).equals(localName) ) {
					context.set(xmlPath.remove(0));
				}
				if ( xmlPath.isEmpty() ) {
					if ( tmpResult == null ) {
						tmpResult = new HashMap<String, Object>();
						for ( String xmlTag : xmlTagsList ) {
							tmpResult.put(xmlTag, null);//SETUP FILTER BY REQUESTED FIELDS ONLY
						}
						context.set(tmpResult);
					}
				}
			} else {//SETUP QUERY RESULT
				Map<String, Object> map;
				if ( context.get() instanceof Map && (map = (Map<String, Object>) context.get()).containsKey(localName) ) {
					int attributeCount = streamReader.getAttributeCount();
					StringBuilder propertyKey = new StringBuilder(); 
					for ( int i = 0; i < attributeCount; i++ ) {
						propertyKey.append(streamReader.getAttributeLocalName(i)).append('=').append(streamReader.getAttributeValue(i)).append('\n');
					}
					Object prevValue = map.get(localName);
					String elementText = streamReader.getElementText();
					if ( prevValue == null && propertyKey.length() == 0) {
						map.put(localName, elementText);
					} else {
						Properties props;
						if ( prevValue == null ) {
							props = new Properties();
							map.put(localName, props);
						} else {
							if ( prevValue instanceof String ) {//TODO: REFACTORING
								props = new Properties();
								props.setProperty(Integer.toString(props.size() + 1), (String) prevValue);
								map.put(localName, props);
							} else {
								props = (Properties) prevValue;
							}
						}
						//PROPERY KEY CHECK
						if ( propertyKey.length() > 0 ) {//TODO: DUBLICATE EVENTING
							props.put(propertyKey.toString(), elementText);
						} else {
							props.setProperty(Integer.toString(props.size() + 1), elementText);
						}
					}
					
				} else {
					//streamReader.next();
					//if ( streamReader.hasText() ) {
					//	streamReader.next();
					//}
				}
			}
		}
		return 0;
	}

	public Map<Integer, Object> getFirstResult() {
		Map<Integer, Object> result = new HashMap<Integer, Object>();
		try {
			readNextObject();
			int argumentIndex = 0;
			for ( String xmlTag : xmlTagsList ) {
				result.put(argumentIndex++, tmpResult.get(xmlTag));
			}
		} catch (XMLStreamException e) {
			e.printStackTrace(System.out);
			LOG.info("GETTING FIRST RESULT FAILED!", e);
		} catch (FactoryConfigurationError e) {
			e.printStackTrace(System.out);
			LOG.info("GETTING FIRST RESULT FAILED!", e);
		}
		//result.put(0, "");
		return result;
	}

}
